import chalk from "chalk";
import path from "path";
import { ApiCallHttp, ApiReturn, BaseConnection, ConnectionStatus, HttpServer, PrefixLogger, TsrpcError, WsClient } from "tsrpc";
import { BackConfig } from "../models/BackConfig";
import { useAdminToken } from "../models/flows/useAdminToken";
import { ResCreateSubWorld } from "../shared/protocols/masterServer/PtlCreateSubWorld";
import { ReqStartMatch, ResStartMatch } from "../shared/protocols/masterServer/PtlStartMatch";
import { ISubWorldState, MsgUpdateSubWorldState } from "../shared/protocols/worldServer/admin/MsgUpdateSubWorldState";
import { serviceProto, ServiceType } from "../shared/protocols/serviceProto_masterServer";
import { serviceProto as serviceProto_worldServer, ServiceType as ServiceType_World } from "../shared/protocols/serviceProto_worldServer";
import { useSsoWs } from "../models/flows/useToken";
import { DBUserInfo, DBUser } from "./DBUser";
import { EnterSubWorldParams } from "../shared/types/EnterSubWorldParams";
import { TokenUtils } from "../TokenUtils";

export interface MasterServerOptions {
    port: number
}

export type MasterServerConn = BaseConnection<ServiceType> & {
    userInfo: DBUserInfo;
};

export interface ISubWorldServerInfo {
    url: string,
    client: WsClient<ServiceType_World>,
    state?: MsgUpdateSubWorldState,
    /**
     * key: subWorldId
     * value: subWorldConfigId
    */
    subWorldMap: Map<string, ISubWorldState>,
    privateSubWorlds: string[],
}

export class MasterServer {
    readonly server = new HttpServer(serviceProto, {
        port: this.options.port,
        // Remove this to use binary mode (remove from the client too)
        json: true
    });
    readonly logger = this.server.logger;

    /** 已注册的 WorldServer */
    readonly worldServers: ISubWorldServerInfo[] = [];

    constructor(public readonly options: MasterServerOptions) {
        // Flows
        useAdminToken(this.server);
        useSsoWs(this.server);
    }

    async init() {
        await this.server.autoImplementApi(path.resolve(__dirname, './api'));
    }

    async start() {
        await this.server.start();

        // 定时 log 播报子世界状态
        setInterval(() => {
            this.logger.log(`
[MasterServer 状态播报]
  - 已连接 WorldServer=${this.worldServers.count(v => !!v.state)}
  - 连接中 WorldServer=${this.worldServers.count(v => !v.state)}
  - 子世界总数=${this.worldServers.sum(v => v.state?.subWorlds.length ?? 0)}
  - 子世界内用户数=${this.worldServers.sum(v => v.state?.subWorlds.sum(v => v.userNum) ?? 0)}
`);
        }, 15000);

        // 定时执行匹配
        this.startMatch();
    }

    //the world servers call this to register themselves.
    async joinWorldServer(serverUrl: string, privateSubWorldList: string[]): Promise<void> {
        // 已经注册过
        if (this.worldServers.some(v => v.url === serverUrl)) {
            return;
        }

        // Create
        let client = new WsClient(serviceProto_worldServer, {
            server: serverUrl,
            logger: new PrefixLogger({
                logger: this.logger,
                prefixs: [chalk.bgCyan.white(`RS|${serverUrl}`)]
            }),
            heartbeat: {
                interval: 5000,
                timeout: 5000
            },
            logMsg: false
        });

        //this.logger.log(`子世界列表:${subWorldInfo}`);

        // Push
        let worldServer: MasterServer['worldServers'][number] = {
            url: serverUrl,
            client: client,
            subWorldMap: new Map<string, ISubWorldState>(),
            privateSubWorlds: privateSubWorldList
        }
        this.worldServers.push(worldServer);

        // Flows
        client.flows.postDisconnectFlow.push(v => {
            this.worldServers.remove(v1 => v1.client === client);
            return v;
        });
        client.listenMsg('admin/UpdateSubWorldState', msg => {
            worldServer.state = msg;
            worldServer.subWorldMap.clear();
            msg.subWorlds.forEach(v => {
                this.usedSubWorldId[v.displayId] = true;
                /**
                 * @en can be both indexed by instId and displayId
                 * @zh 可以同时通过实例 ID 和显示 ID 查询
                **/
                worldServer.subWorldMap.set(v.id, v);
                worldServer.subWorldMap.set(v.displayId, v);
            });
        });

        client.listenMsg('admin/ChatTransform', msg => {
            this.worldServers.forEach(v => {
                v.client.sendMsg('admin/ChatTransform', msg);
            });
        });

        try {
            // Connect
            let op = await client.connect();
            if (!op.isSucc) {
                throw new TsrpcError(op.errMsg);
            }

            // Auth as MasterServer
            let op2 = await client.callApi('admin/Auth', {
                adminToken: BackConfig.adminToken,
                type: 'MasterServer'
            });
            if (!op2.isSucc) {
                client.disconnect();
                throw op2.err;
            }
        }
        catch (e: unknown) {
            this.worldServers.remove(v => v.url === serverUrl);
            throw e;
        }

        this.logger.log(chalk.green(`World server joined: ${serverUrl}, worldServers.length=${this.worldServers.length}`))
    }

    public getSubWorldServer(subWorldId: string) {
        for (let i = 0; i < this.worldServers.length; ++i) {
            let s = this.worldServers[i];
            if (s.subWorldMap.has(subWorldId)) {
                return s;
            }
        }
    }

    // #region 匹配相关
    /** 待匹配队列 */
    matchQueue = new Set<ApiCallHttp<ReqStartMatch, ResStartMatch>>();

    async startMatch() {
        await this._doMatch().catch(e => {
            this.server.logger.error('[MatchError]', e);
        })

        setTimeout(() => { this.startMatch() }, BackConfig.masterServer.intervalMatch)
    }

    async quickMatch(call: ApiCallHttp<ReqStartMatch, ResStartMatch>) {
        let bestMatchedSubWorld = this.getBestMatchedNoFullSubWorld(call.req.type);
        // 尝试匹配，你可以在此实现自己的匹配规则            
        // 这里简单起见，优先匹配人多的子世界
        let serverUrl: string = '';
        let subWorldId: string = '';

        if (bestMatchedSubWorld.subWorld) {
            serverUrl = bestMatchedSubWorld.url;
            subWorldId = bestMatchedSubWorld.subWorld!.id;
        }
        else {
            // 没有合适的子世界，那么创建一个子世界
            let retCreateSubWorld = await this.createSubWorld('', call.req.type, '');
            if (retCreateSubWorld.isSucc) {
                serverUrl = retCreateSubWorld.res.serverUrl;
                subWorldId = retCreateSubWorld.res.subWorldId;
            }
        }
        if (serverUrl && subWorldId) {
            let conn = call.conn as any as MasterServerConn;

            let enterParams = this.createEnterSubWorldParams(
                conn.userInfo.uid!,
                serverUrl,
                subWorldId,
                call.req.type);
            call.succ(enterParams);

            if (call.req.type != 'lobby') {
                DBUser.updateUserDataByUid(conn.userInfo.uid!, { subWorldId: subWorldId });
            }
            return true;
        }

        return false;
    }


    getBestMatchedNoFullSubWorld(configId: string): { url: string, subWorld: ISubWorldState | null } {
        let url = '';
        let subWorld: ISubWorldState | null = null;
        let minPlayerNum = 1000000000;
        this.worldServers.forEach(subWorldServer => {
            subWorldServer.state?.subWorlds.forEach(v => {
                if (v.configId == configId && v.playerNum < v.maxPlayerNum && !v.password && v.playerNum < minPlayerNum) {
                    url = subWorldServer.url;
                    subWorld = v;
                    minPlayerNum = v.playerNum;
                }
            });
        });

        return { url: url, subWorld: subWorld };
    }


    /**
     * 执行一次匹配
     */
    private async _doMatch() {
        this.logger.log(`匹配开始，匹配人数=${this.matchQueue.size}`);
        let succNum = 0;

        for (let call of this.matchQueue) {

            let conn = call.conn as any as MasterServerConn;
            // 连接已断开，不再匹配
            if (call.conn.status !== ConnectionStatus.Opened) {
                this.matchQueue.delete(call);
                return;
            }

            let bSuc = await this.quickMatch(call);
            if (bSuc) {
                succNum++;
                this.matchQueue.delete(call);
            }

        }

        this.logger.log(`匹配结束，成功匹配人数=${succNum}`)
    }
    // #endregion

    usedSubWorldId = {} as any;
    genSubWorldId() {
        let subWorldId = '';
        while (true) {
            subWorldId = TokenUtils.genID(6, true);
            if (!this.usedSubWorldId[subWorldId]) {
                break;
            }
        }
        this.usedSubWorldId[subWorldId] = subWorldId;
        return subWorldId;
    }

    getMinLoadWorldServer(subWorldConfigId: string) {
        let minLoadServer = null;
        for (let i = 0; i < this.worldServers.length; ++i) {
            let ws = this.worldServers[i];
            if (ws.privateSubWorlds.indexOf(subWorldConfigId) != -1) {
                if (minLoadServer == null || ws.state!.connNum < minLoadServer.state!.connNum) {
                    minLoadServer = ws;
                }
            }
        }

        return minLoadServer;
    }

    async createSubWorld(subWorldName: string, subWorldConfigId: string, password: string): Promise<ApiReturn<ResCreateSubWorld>> {
        // 挑选一个人数最少的 WorldServer
        let worldServer = this.getMinLoadWorldServer(subWorldConfigId);
        if (!worldServer) {
            return { isSucc: false, err: new TsrpcError('没有可用的世界服务器') };
        }

        let displayId = this.genSubWorldId();
        let subWorldId = Date.now() + displayId;
        // RPC -> WorldServer
        let op = await worldServer.client.callApi('admin/CreateSubWorld', {
            adminToken: BackConfig.adminToken,
            subWorldName: subWorldName,
            subWorldId: subWorldId,
            displayId: displayId,
            subWorldConfigId: subWorldConfigId,
            password: password,
        })
        if (!op.isSucc) {
            return { isSucc: false, err: new TsrpcError(op.err) };
        }

        // Return
        return {
            isSucc: true,
            res: {
                subWorldId: op.res.subWorldId,
                serverUrl: worldServer.url
            }
        }
    }

    public createEnterSubWorldParams(uid: string, worldServerUrl: string, subWorldId: string, subWorldConfigId: string): EnterSubWorldParams {
        let url = worldServerUrl;
        let time = Math.floor(Date.now() / 1000);
        let token = TokenUtils.genWorldServerLoginToken(uid, url, subWorldId, time);
        return { subWorldId: subWorldId, subWorldConfigId: subWorldConfigId, worldServerUrl: url, token: token, time: time };
    }
}